using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;

using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>
    /// <b>CalendarPopup</b> control bound to a business class property.
    /// </summary>
    [DefaultProperty("SelectedValue"), ToolboxData("<{0}:BoundCalendar runat=server></{0}:BoundCalendar>")]
    public class BoundCalendar : eWorld.UI.CalendarPopup, IBoundControl, IValidator
    {
        Label errorLbl = new Label();

        #region FieldPermissions.

        FieldAccessMode accessMode = FieldAccessMode.Automatic;
        string editCss = "";
        string readOnlyCss = "";

        IBindableField bizField;
        Color errorColor = Color.Red;
        //string errorCss;
        //string errorMessage = "";
        //string errorIndicator = "*";
        ErrorLocation errorLocation = ErrorLocation.Below;

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of the <see cref="BoundCalendar"/> class.
        /// </summary>
        public BoundCalendar()
        {
            this.UpperBoundDate = new DateTime(9999, 12, 31, 23, 59, 59);
            this.Culture = System.Globalization.CultureInfo.InvariantCulture;
        }

        #endregion

        #region OnInit.

        /// <summary>
        /// Raises the <b>Init</b> event.
        /// </summary>
        /// <param name="e">An <b>EventArgs</b> object that contains the event data.</param>
        protected override void  OnInit(EventArgs e)
        {
 	         base.OnInit(e);
             AddMyselfToParentPageValidators();
        }

        private void AddMyselfToParentPageValidators()
        {
            this.Page.Validators.Add(this);
        }

        #endregion

        #region Error properties.

        /// <summary>
        /// Gets or sets the CSS Class name applied to the error label.
        /// </summary>
        [Bindable(false), Category("Appearance"), DefaultValue(null),
        Description("CSS Class name applied to the error label.")]
        public virtual string ErrorCss
        {
            get { return this.errorLbl.CssClass; }
            set { this.errorLbl.CssClass = value; }
        }

        /// <summary>
        /// Gets or sets the color of the error text.
        /// </summary>
        [Bindable(false), Category("Appearance"), DefaultValue(typeof(Color), "Red"),
        Description("Color of the error text.")]
        [TypeConverter(typeof(WebColorConverter))]
        public virtual Color ErrorColor
        {
            get { return this.errorColor; }
            set { this.errorColor = value; }
        }

        /// <summary>
        /// Gets or sets the error text displayed in the error label or in the page's validation summary. 
        /// Usually set programmatically.
        /// </summary>
        [Bindable(false), Category("Appearance"),
        Description("Error text displayed in the error label or in the page's validation summary. Usually set programmatically.")]
        [DefaultValue("")]
        public virtual string ErrorText
        {
            get { return this.errorLbl.Text; }
            set { this.errorLbl.Text = value; }
        }

        /// <summary>
        /// Gets or sets the indicator placed by the control if it contains an invalid value. 
        /// Displayed only when the error text is displayed in the validation summary.
        /// </summary>
        [Bindable(false), Category("Appearance"), DefaultValue("*"),
        Description("Indicator placed by the control if it contains an invalid value. Displayed only when the error text is displayed in the validation summary.")]
        public virtual string ErrorIndicator
        {
            get { return "*"; }
            set { }
        }

        /// <summary>
        /// Gets or sets the value which specifies where the error text is located.
        /// </summary>
        [Bindable(false), Category("Appearance"), DefaultValue(typeof(ErrorLocation), "Below"),
        Description("Specifies where the error text is located.")]
        public virtual ErrorLocation ErrorLocation
        {
            get { return this.errorLocation; }
            set { this.errorLocation = value; }
        }

        #endregion

        #region Control properties.

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class rendered by the Web server control on the client.
        /// </summary>
        [Bindable(false), Category("Behavior"), DefaultValue(typeof(string), ""),
        Description("Cascading Style Sheet (CSS) class rendered by the Web server control on the client.")]
        public override string CssClass
        {
            get
            {
                return this.editCss;
            }
            set
            {
                this.editCss = (value != null) ? value.Trim() : "";
                base.CssClass = this.editCss;
            }
        }

        /// <summary>
        /// Gets or sets the Cascading Style Sheet (CSS) class rendered by the Web server control 
        /// on the client when the control is in read-only mode.
        /// </summary>
        [Bindable(false), Category("Behavior"), DefaultValue(typeof(string), ""),
        Description("Cascading Style Sheet (CSS) class rendered by the Web server control on the client when the control is in read-only mode.")]
        public string ReadOnlyCssClass
        {
            get { return this.readOnlyCss; }
            set { this.readOnlyCss = (value != null) ? value.Trim() : ""; }
        }

        bool fieldCaptionIsDefaultToolTip = true;

        /// <summary>
        /// Gets or sets whether the caption of the bound field is used as default tooltip.
        /// </summary>
        /// <value><b>true</b> if field caption is default tooltop; <b>false</b> otherwise. Default is <b>true</b>.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(true)]
        [Description("Whether the caption of the bound field is used as default tooltip.")]
        public virtual bool FieldCaptionIsDefaultToolTip
        {
            get { return this.fieldCaptionIsDefaultToolTip; }
            set { this.fieldCaptionIsDefaultToolTip = value; }
        }

        /// <summary>
        /// Gets the ClientID assigned to the textbox control used in the popup control.
        /// </summary>
        [Bindable(false)]
        [Browsable(false)]
        [Category("Misc")]
        [Description("ClientID assigned to the textbox control used in the popup control.")]
        public virtual string TextBoxClientID
        {
            get { return this.TextBox.ClientID; }
        }

        /// <summary>
        /// Gets the ClientID assigned to the image control used in the popup control.
        /// </summary>
        [Bindable(false)]
        [Browsable(false)]
        [Category("Misc")]
        [Description("ClientID assigned to the image control used in the popup control.")]
        public virtual string ImageClientID
        {
            get { return this.Image.ClientID; }
        }

        #endregion

        #region IBoundControl.

        /// <summary>
        /// Gets or sets the value which indicates whether the <b>CalendarPopup</b> can change data in the bound field.
        /// </summary>
        /// <remarks>Setting this property to <b>ReadOnly</b> will prevent users from changing the <b>SelectedValue</b> property.</remarks>
        [Bindable(false), Category("Behavior"), DefaultValue(typeof(FieldAccessMode), "Automatic"),
        Description("Indicates whether the contents of the control can be changed.")]
        public FieldAccessMode AccessMode
        {
            get { return this.accessMode; }
            set { this.accessMode = value; }
        }

        /// <summary>
        /// Gets or sets the business class' boolean property to which the control is bound. Set programmatically.
        /// </summary>
        [Description("Business class' property to which the control is bound. Set programmatically.")]
        [Browsable(false)]
        public virtual IBindableField BusinessField
        {
            get { return this.bizField; }
            set { this.bizField = value; }
        }

        /// <summary>
        /// Appends all errors in the collection to the <see cref="ErrorText"/>.
        /// </summary>
        /// <param name="rules">Collection of broken rules.</param>
        /// <param name="formatter">The <see cref="IMessageFormatter"/> object used to format the messages contained in the collection.</param>
        public virtual void AppendError(BusinessRuleCollection rules, IMessageFormatter formatter)
        {
            BusinessRuleCollection brokenRulesForThisControl = rules.GetRulesForField(this.BusinessField.BindingName, true);
            this.ErrorText += brokenRulesForThisControl.ToString(formatter) + " ";
        }

        /// <summary>
        /// Clears errors. Same as setting the <see cref="ErrorText"/> to empty string.
        /// </summary>
        public virtual void ClearError()
        {
            this.ErrorText = "";
        }

        #endregion

        #region IValidator.

        /// <summary>
        /// Gets the value contained in the <see cref="ErrorText"/> property if a validation summary has been specified as location for errors.
        /// </summary>
        string IValidator.ErrorMessage
        {
            get
            {
                if (this.errorLocation == ErrorLocation.ValidationSummary)
                    return this.ErrorText;
                else
                    return "";
            }
            set
            {
            }
        }

        /// <summary>
        /// Does nothing. Validation logic is performed elsewhere (eg. business class).
        /// </summary>
        void IValidator.Validate()
        {
        }

        /// <summary>
        /// Gets a value which indicates whether an the <see cref="ErrorText"/> property contains error description.
        /// </summary>
        bool IValidator.IsValid
        {
            get
            {
                if (this.ErrorText == null || this.ErrorText.Trim() == "")
                    return true;
                else
                    return false;
            }
            set
            {
            }
        }

        #endregion

        #region Render.

        /// <summary>
        /// Sends server control content to a provided <see cref="HtmlTextWriter"/> object, which writes the content to be rendered on the client.
        /// </summary>
        /// <param name="output">The <b>HtmlTextWriter</b> object that receives the server control content.</param>
        protected override void Render(HtmlTextWriter output)
        {
            if (this.FieldCaptionIsDefaultToolTip && string.IsNullOrEmpty(this.ToolTip) && this.bizField != null)
                this.ToolTip = bizField.Caption;

            if (IsInReadOnlyMode())
            {
                // Hide text-box but render it.
                RenderAsReadOnlyLabel(output);
                this.Button.Visible = false;
                this.Image.Visible = false;
                this.Label.Style["display"] = "none";
                this.TextBox.Style["display"] = "none";
            }

            base.Render(output);
            RenderErrorMessage(output);

            // Test later if the following works...

            /*
            if (this.FieldCaptionIsDefaultToolTip && string.IsNullOrEmpty(this.ToolTip) && this.bizField != null)
                this.ToolTip = bizField.Caption;

            if (IsInReadOnlyMode())
            {
                // Hide text-box but render it.
                RenderAsReadOnlyLabel(output);
                
                // Calendar popup uses a few HtmlInputHidden controls to store all relevant data,
                // and references them in JavaScript constructors. Remove all other controls.
                foreach (Control ctl in this.Controls)
                {
                    bool isRequired = (ctl is System.Web.UI.HtmlControls.HtmlInputHidden);
                    if (!isRequired)
                        ctl.Visible = false;
                }
            }

            base.Render(output);
            RenderErrorMessage(output);
            */
        }

        private bool IsInReadOnlyMode()
        {
            bool isReadOnly;
            switch (this.AccessMode)
            {
                case FieldAccessMode.Automatic:
                    if (this.bizField != null && this.bizField.IsReadOnly)
                        isReadOnly = true;
                    else
                        isReadOnly = false;
                    break;
                case FieldAccessMode.Edit:
                    isReadOnly = false;
                    break;
                case FieldAccessMode.ReadOnly:
                    isReadOnly = true;
                    break;
                default:
                    throw new Exception("Unsupported field access mode: " + this.accessMode.ToString() + ".");
            }

            return isReadOnly;
        }

        private void RenderAsReadOnlyLabel(HtmlTextWriter output)
        {
            Label lbl = new Label();
            lbl.CopyBaseAttributes(this);
            lbl.CssClass = (this.readOnlyCss != "") ? this.readOnlyCss : this.editCss;
            lbl.Text = (string.IsNullOrEmpty(this.TextBox.Text)) ? this.Label.Text : this.TextBox.Text;
            lbl.BackColor = this.BackColor;
            lbl.RenderControl(output);
        }

        private void RenderErrorMessage(HtmlTextWriter output)
        {
            if (this.errorLbl.Text != null && this.errorLbl.Text.Trim() != "")
            {
                this.errorLbl.ForeColor = this.ErrorColor;
                this.errorLbl.EnableViewState = false;
                switch (this.errorLocation)
                {
                    case ErrorLocation.Below:
                        output.Write("<br />");
                        break;

                    case ErrorLocation.Right:
                        output.Write("&nbsp;");
                        break;

                    case ErrorLocation.ValidationSummary:
                        output.Write("&nbsp;");                                                
                        break;
                }

                this.errorLbl.RenderControl(output);
            }
        }

        #endregion

        #region Properties with overriden attributes - required for "Local RESX Generator" tool compliance.

        /// <summary>
        /// Gets or sets how the control is displayed to the user using the DisplayType enumeration.
        /// </summary>
        [DefaultValue(typeof(eWorld.UI.DisplayType), "TextBoxButton")]
        public override eWorld.UI.DisplayType ControlDisplay
        {
            get { return base.ControlDisplay; }
            set { base.ControlDisplay = value; }
        }

        /// <summary>
        /// Gets and sets the DayNameFormat for displaying the day name.
        /// </summary>
        [DefaultValue(typeof(DayNameFormat), "FirstLetter")]
        public override DayNameFormat DayNameFormat
        {
            get { return base.DayNameFormat; }
            set { base.DayNameFormat = value; }
        }

        /// <summary>
        /// Gets or sets the display action taken when DisabledTextboxEntry is true.
        /// </summary>
        [DefaultValue(typeof(eWorld.UI.DisplayAction), "OnClick")]
        public override eWorld.UI.DisplayAction DisabledEntryDisplayAction
        {
            get { return base.DisabledEntryDisplayAction; }
            set { base.DisabledEntryDisplayAction = value; }
        }

        /// <summary>
        /// Gets or sets the location that the popup will be positioned.
        /// </summary>
        [DefaultValue(typeof(eWorld.UI.DisplayLocation), "Right")]
        public override eWorld.UI.DisplayLocation PopupLocation
        {
            get { return base.PopupLocation; }
            set { base.PopupLocation = value; }
        }

        /// <summary>
        /// Gets the posted date sent back via the server. Also contains the text if allowing arbitrary text. This is used for the validation property.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override string PostedDate
        {
            get { return base.PostedDate; }
            set { base.PostedDate = value; }
        }

        /// <summary>
        /// Gets or sets the selected date in the popup calendar control.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override DateTime SelectedDate
        {
            get {  return base.SelectedDate; }
            set { base.SelectedDate = value; }
        }

        /// <summary>
        /// Selected value used in the popup control.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new DateTime? SelectedValue
        {
            get { return base.SelectedValue; }
            set { base.SelectedValue = value; }
        }

        /// <summary>
        /// Gets or sets the upper bounded date the user can select.
        /// </summary>
        [DefaultValue(typeof(DateTime), "12/31/9999 23:59:59")]
        public override DateTime UpperBoundDate
        {
            get { return base.UpperBoundDate; }
            set { base.UpperBoundDate = value; }
        }

        /// <summary>
        /// Gets and sets the date that is initially visible when the calendar loads.
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override DateTime VisibleDate
        {
            get { return base.VisibleDate;  }
            set { base.VisibleDate = value; }
        }

        #endregion

        #region TextSource, BoundCalendarTextSourceFactory, NullBoundCalendarTextSource.

        IBoundCalendarTextSource textSource = BoundCalendarTextSourceFactory.GetTextSource();

        /// <summary>Gets source of captions and messages configured in application's settings file.</summary>
        /// <value>An <see cref="IBoundCalendarTextSource"/> object which contains captions and messages that override values set through BoundCalendar properties.</value>
        /// <remarks><para>Settings are read from the application's config file as shown in the following example:
        /// <code>
        /// <configuration>
        ///     <appSettings>
        ///         <add key="BizElements.Web.BoundCalendar.TextSourceAssembly" value="BizElements.Web" />
        ///         <add key="BizElements.Web.BoundCalendar.TextSourceClass" value="BizElements.Web.BoundCalendarTextSource" />
        ///     </appSettings>
        /// </configuration>
        /// </code></para>
        /// <para>If the previus settings are not found in cofiguration file, then a dummy implementation which doesn't override BoundCalendar's properties
        /// is used, i.e. an implementation that returns null values.</para>
        /// </remarks>
        public IBoundCalendarTextSource TextSource
        {
            get { return this.textSource; }
        }

        #endregion

        #region Overriden multilingual properties.

        /// <summary>Gets or sets the clear date text displayed when the calendar is rendered.</summary>
        [DefaultValue("Clear Date"), Description("The clear date text displayed when the calendar is rendered."), Category("Appearance"), Localizable(true)]
        public override string ClearDateText
        {
            get { return this.TextSource.ClearDateText ?? base.ClearDateText; }
            set { base.ClearDateText = value; }
        }

        /// <summary>Gets or sets the go to today text displayed when the calendar is rendered.</summary>
        [DefaultValue("Today's Date:"), Localizable(true), Description("The go to today text displayed when the calendar is rendered."), Category("Appearance")]
        public override string GoToTodayText
        {
            get { return this.TextSource.GoToTodayText ?? base.GoToTodayText; }
            set { base.GoToTodayText = value; }
        }

        /// <summary>Gets or sets the text displayed for the apply button on the month / year popup.</summary>
        [Description("The text displayed for the apply button on the month / year popup."), DefaultValue("Apply"), Localizable(true), Category("Appearance")]
        public override string MonthYearApplyText
        {
            get { return this.TextSource.MonthYearApplyText ?? base.MonthYearApplyText; }
            set { base.MonthYearApplyText = value; }
        }

        /// <summary>Gets or sets the text displayed for the cancel button on the month / year popup.</summary>
        [Description("The text displayed for the cancel button on the month / year popup."), DefaultValue("Cancel"), Localizable(true), Category("Appearance")]
        public override string MonthYearCancelText
        {
            get { return this.TextSource.MonthYearCancelText ?? base.MonthYearCancelText; }
            set { base.MonthYearCancelText = value; }
        }

        /// <summary>Gets or sets the text displayed for the label when it is nulled out.</summary>
        [Localizable(true), Description("The text displayed for the label when it is nulled out."), Category("Appearance"), DefaultValue("Select a Date")]
        public override string NullableLabelText
        {
            get { return this.TextSource.NullableLabelText ?? base.NullableLabelText; }
            set { base.NullableLabelText = value; }
        }

        #endregion

        /// <summary>Gets the name of the control property which is bound two-way to a business object field: <b>SelectedValue</b></summary>
        [Browsable(false)]
        public string TwoWayBoundProperty
        {
            get { return "SelectedValue"; }
        }

        /// <summary>Returns <b>null</b>.</summary>
        [Browsable(false)]
        public string[] OneWayBoundProperties
        {
            get { return null; }
        }

        /// <summary>Gets error label control which displays errors associated with the given field.</summary>
        [Browsable(false)]
        public ITextControl ErrorLabel
        {
            get { return this.errorLbl; }
        }

        /// <summary>Not implemented. Returns <b>null</b>.</summary>
        [Browsable(false)]
        ITextControl IBoundControl.CaptionLabel
        {
            get { return null; }
        }

        /// <summary>Not implemented. Returns <b>null</b>.</summary>
        [Browsable(false)]
        ITextControl IBoundControl.TooltipLabel
        {
            get { return null; }
        }

        /// <summary>Not implemented. Returns <b>null</b>.</summary>
        [Browsable(false)]
        Control IBoundControl.RequiredFieldIndicatorControl
        {
            get { return null; }
        }

        /// <summary>Gets or sets the culture of the popup.</summary>
        [DefaultValue(typeof(CultureInfo), "(Default)")]
        public override CultureInfo Culture
        {
            get
            {
                if (this.UseSessionCulture)
                    return UserSession.UiCulture;
                else
                    return base.Culture;
            }
            set
            {
                if (!this.UseSessionCulture)
                    base.Culture = value;
            }
        }

        /// <summary>Gets or sets whether the <see cref="Culture"/> property can be manually set or whether <see cref="UserSession.UiCulture"/> is used.</summary>
        [Bindable(false), Category("Behavior"), DefaultValue(typeof(bool), "false"),
        Description("Indicates whether Culture property can be manually set or whether UserSession.UiCulture is used.")]
        public virtual bool UseSessionCulture { get; set; }
    }
}
